﻿'****************************** Module Header ******************************'
' Module Name:  HotKeyRegister.vb
' Project:	    VBRegisterHotkey
' Copyright (c) Microsoft Corporation.
' 
' This class imports the method RegisterHotKey and UnregisterHotKey in 
' user32.dll to define or free a system-wide hot key.
' 
' The method Application.AddMessageFilter is used to add a message filter to 
' monitor Windows messages as they are routed to their destinations. Before 
' a message is dispatched, the method PreFilterMessage could handle it. If a 
' WM_HOTKEY messages was generated by the hot key that was registered by this 
' HotKeyRegister object, then raise a HotKeyPressed event.
' 
' This class also supplies a static method GetModifiers to get the modifiers 
' and key from the KeyData property of KeyEventArgs.
' 
' This source is subject to the Microsoft Public License.
' See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
' All other rights reserved.
' 
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
' WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
'*************************************************************************'

Imports System.Runtime.InteropServices
Imports System.Security.Permissions
Imports System.Windows.Forms

Namespace Win32
    Public Class HotKeyRegister
        Implements IMessageFilter, IDisposable

        ''' <summary>
        ''' Define a system-wide hot key.
        ''' </summary>
        ''' <param name="hWnd">
        ''' A handle to the window that will receive WM_HOTKEY messages generated by the
        ''' hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the 
        ''' message queue of the calling thread and must be processed in the message loop.
        ''' </param>
        ''' <param name="id">
        ''' The identifier of the hot key. If the hWnd parameter is NULL, then the hot 
        ''' key is associated with the current thread rather than with a particular 
        ''' window. If a hot key already exists with the same hWnd and id parameters, 
        ''' see Remarks for the action taken.
        ''' </param>
        ''' <param name="fsModifiers">
        ''' The keys that must be pressed in combination with the key specified by the 
        ''' uVirtKey parameter in order to generate the WM_HOTKEY message. The fsModifiers
        ''' parameter can be a combination of the following values.
        ''' MOD_ALT     0x0001
        ''' MOD_CONTROL 0x0002
        ''' MOD_SHIFT   0x0004
        ''' MOD_WIN     0x0008
        ''' </param>
        ''' <param name="vk">The virtual-key code of the hot key.</param>
        <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
        Private Shared Function RegisterHotKey(ByVal hWnd As IntPtr,
                                           ByVal id As Integer,
                                           ByVal fsModifiers As KeyModifiers,
                                           ByVal vk As Keys) As Boolean
        End Function

        ''' <summary>
        ''' Frees a hot key previously registered by the calling thread. 
        ''' </summary>
        ''' <param name="hWnd">
        ''' A handle to the window associated with the hot key to be freed. This parameter
        ''' should be NULL if the hot key is not associated with a window.
        ''' </param>
        ''' <param name="id">
        ''' The identifier of the hot key to be freed. 
        ''' </param>
        <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
        Private Shared Function UnregisterHotKey(ByVal hWnd As IntPtr,
                                             ByVal id As Integer) As Boolean
        End Function

        ''' <summary>
        ''' Get the modifiers and key from the KeyData property of KeyEventArgs.
        ''' </summary>
        ''' <param name="keydata">
        ''' The KeyData property of KeyEventArgs. The KeyData is a key in combination
        ''' with modifiers.
        ''' </param>
        ''' <param name="key">The pressed key.</param>
        Public Shared Function GetModifiers(ByVal keydata As Keys,
                                            ByRef key As Keys) As KeyModifiers

            key = keydata
            Dim modifers As KeyModifiers = KeyModifiers.None

            ' Check whether the keydata contains the CTRL modifier key.
            ' The value of Keys.Control is 131072.
            If (keydata And Keys.Control) = Keys.Control Then
                modifers = modifers Or KeyModifiers.Control

                key = keydata Xor Keys.Control
            End If

            ' Check whether the keydata contains the SHIFT modifier key.
            ' The value of Keys.Control is 65536.
            If (keydata And Keys.Shift) = Keys.Shift Then
                modifers = modifers Or KeyModifiers.Shift
                key = key Xor Keys.Shift
            End If

            ' Check whether the keydata contains the ALT modifier key.
            ' The value of Keys.Control is 262144.
            If (keydata And Keys.Alt) = Keys.Alt Then
                modifers = modifers Or KeyModifiers.Alt
                key = key Xor Keys.Alt
            End If

            ' Check whether a key other than SHIFT, CTRL or ALT (Menu) is pressed.
            If key = Keys.ShiftKey OrElse key = Keys.ControlKey _
                OrElse key = Keys.Menu Then
                key = Keys.None
            End If

            Return modifers
        End Function

        ''' <summary>
        ''' Specify whether this object is disposed.
        ''' </summary>
        Private _disposed As Boolean = False

        ''' <summary>
        ''' This constant could be found in WinUser.h if you installed Windows SDK.
        ''' Each windows message has an identifier, 0x0312 means that the mesage is 
        ''' a WM_HOTKEY message.
        ''' </summary>
        Private Const WM_HOTKEY As Integer = &H312

        ''' <summary>
        ''' A handle to the window that will receive WM_HOTKEY messages generated by the
        ''' hot key.
        ''' </summary>
        Private _handle As IntPtr
        Public Property Handle() As IntPtr
            Get
                Return _handle
            End Get
            Private Set(ByVal value As IntPtr)
                _handle = value
            End Set
        End Property

        Private _id As Integer
        Public Property ID() As Integer
            Get
                Return _id
            End Get
            Private Set(ByVal value As Integer)
                _id = value
            End Set
        End Property

        Private _modifiers As KeyModifiers
        Public Property Modifiers() As KeyModifiers
            Get
                Return _modifiers
            End Get
            Private Set(ByVal value As KeyModifiers)
                _modifiers = value
            End Set
        End Property

        Private _key As Keys
        Public Property Key() As Keys
            Get
                Return _key
            End Get
            Private Set(ByVal value As Keys)
                _key = value
            End Set
        End Property


        ''' <summary>
        ''' Raise an event when the hotkey is pressed.
        ''' </summary>
        Public Event HotKeyPressed As EventHandler

        Public Sub New(ByVal handle As IntPtr, ByVal id As Integer,
                       ByVal modifiers As KeyModifiers, ByVal key As Keys)
            If key = Keys.None OrElse modifiers = KeyModifiers.None Then
                Throw New ArgumentException("The key or modifiers could not be None.")
            End If

            Me.Handle = handle
            Me.ID = id
            Me.Modifiers = modifiers
            Me.Key = key

            RegisterHotKey()

            ' Adds a message filter to monitor Windows messages as they are routed to
            ' their destinations.
            Application.AddMessageFilter(Me)

        End Sub


        ''' <summary>
        ''' Register the hotkey.
        ''' </summary>
        Private Sub RegisterHotKey()
            Dim isKeyRegisterd As Boolean = RegisterHotKey(Handle, ID, Modifiers, Key)

            ' If the operation failed, try to unregister the hotkey if the thread 
            ' has registered it before.
            If Not isKeyRegisterd Then

                ' IntPtr.Zero means the hotkey registered by the thread.
                UnregisterHotKey(IntPtr.Zero, ID)

                ' Try to register the hotkey again.
                isKeyRegisterd = RegisterHotKey(Handle, ID, Modifiers, Key)

                ' If the operation still failed, it means that the hotkey was already 
                ' used in another thread or process.
                If Not isKeyRegisterd Then
                    Throw New ApplicationException("The hotkey is in use.")
                End If
            End If
        End Sub


        ''' <summary>
        ''' Filters out a message before it is dispatched.
        ''' </summary>
        <PermissionSetAttribute(SecurityAction.LinkDemand, Name:="FullTrust")>
        Public Function PreFilterMessage(ByRef m As Message) As Boolean _
    Implements IMessageFilter.PreFilterMessage
            ' The property WParam of Message is typically used to store small pieces 
            ' of information. In this scenario, it stores the ID.
            If m.Msg = WM_HOTKEY AndAlso m.HWnd = Me.Handle _
                AndAlso m.WParam = New IntPtr(Me.ID) Then

                ' Raise the HotKeyPressed event if it is an WM_HOTKEY message.
                RaiseEvent HotKeyPressed(Me, EventArgs.Empty)

                ' True to filter the message and stop it from being dispatched.
                Return True
            End If

            ' False to allow the message to continue to the next filter or control.
            Return False
        End Function


        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub


        ''' <summary>
        ''' Unregister the hotkey.
        ''' </summary>
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            ' Protect from being called multiple times.
            If _disposed Then
                Return
            End If

            If disposing Then

                ' Removes a message filter from the message pump of the application.
                Application.RemoveMessageFilter(Me)

                UnregisterHotKey(Handle, ID)
            End If

            _disposed = True
        End Sub

    End Class
End Namespace